﻿using System;
using System.Collections.Generic;
using System.Threading;
using System.Text;

using System.IO;
using System.Net;
using System.Net.Sockets;

namespace zmMoniter
{
    public delegate void ClientConnectedEventArgsHandler(object sender, ClientConnectedEventArgs e);        //处理客户端连接成功事件的委托
    public delegate void ClientCutConnectionEventHAndler(object sender, ClientCutConnectionEventArgs e);   //处理客户端已经断开事件的委托
    public delegate void ServerReceiveMessageEventHander(object sender, ServerReceiveMessageEventArgs e);   //处理收到消息事件的委托 

    /// <summary>
    /// TcpUtil的服务器端组件类
    /// 提供简单易用的Tcp通信功能
    /// </summary>
    public class Server
    {

        #region --事件--

        public event ClientConnectedEventArgsHandler connected;
        public event ClientCutConnectionEventHAndler clientCutConnection;
        public event ServerReceiveMessageEventHander receiveMesage;
        

        #endregion

        #region -- 私有变量 --

        private IPEndPoint localIPEndPoint;
        private LinkedList<ClientInfo> clientList;
        private Boolean logEnable;
        private Int32 timeOut = 30000;
        private const Int32 BUFFERSIZE = 4096;

        private TcpListener listener;
        private String logPath = System.AppDomain.CurrentDomain.BaseDirectory.ToString() + "系统日志.txt";

        private Thread checkTimeThread;

        #endregion

        #region -- 属性 --

        /// <summary>
        /// 本地终结点
        /// </summary>
        public IPEndPoint LocalIPEndPoint
        {
            get { return localIPEndPoint; }
            set { localIPEndPoint = value; }
        }

        /// <summary>
        /// 客户端信息列表
        /// </summary>
        public LinkedList<ClientInfo> ClientList
        {
            get { return clientList; }
            set { clientList = value; }
        }

        /// <summary>
        /// 是否开启日志
        /// </summary>
        public Boolean LogEnable
        {
            get { return logEnable; }
            set { logEnable = value; }
        }

        /// <summary>
        /// 判断客户端是否掉线的时间
        /// </summary>
        public Int32 TimeOut
        {
            get { return timeOut; }
            set { timeOut = value; }
        }

        #endregion

        #region -- 外部方法 --

        /// <summary>
        /// 构造方法
        /// </summary>
        /// <param name="localIPEndPoint">本地终结点</param>
        /// <param name="logEnable">是否开启日志</param>
        /// <param name="timeOut">判断客户端是否掉线的时间</param>
        public Server(IPEndPoint localIPEndPoint, Boolean logEnable, Int32 timeOut)
        {
            this.localIPEndPoint = localIPEndPoint;
            this.logEnable = logEnable;
            this.timeOut = timeOut;
            clientList = new LinkedList<ClientInfo>();
        }

        /// <summary>
        /// 启动服务
        /// </summary>
        public Int32 Start()
        {
            Int32 result = 0;
            try
            {
                listener = new TcpListener(localIPEndPoint);
                listener.Start();

                listener.BeginAcceptTcpClient(new AsyncCallback(OnConnectRequest), listener);

                //checkTimeThread = new Thread(new ThreadStart(CheckTime));
                //checkTimeThread.IsBackground = true;
                //checkTimeThread.Start();

                this.WriteLog(this.logEnable, this.logPath, "开始监听" + localIPEndPoint + "时间:" + DateTime.Now);

                result = 1;

            }
            catch (Exception ex)
            {
                result = 0;
                this.WriteLog(this.logEnable, this.logPath, "开启监听失败!原因:发生未知异常. 时间:" + DateTime.Now + "/r/n" + "异常信息:" + ex.ToString());
            }

            return result;
        }

        /// <summary>
        /// 结束服务
        /// </summary>
        public Int32 End()
        {
            try
            {
                checkTimeThread.Abort();
            }
            catch (Exception ex)
            {
                this.WriteLog(this.logEnable, this.logPath, "在退出检查线程是发生异常,次异常是在结束服务时发生的不需要处理./r/n异常原因:/r/n" + ex.ToString());
            }

            Int32 result = 0;
            try
            {
                if (listener != null)
                {
                    if (clientList.Count > 0)
                    {
                        //循环删除链表
                        //byte[] message = Encoding.ASCII.GetBytes("($KICK$)");
                        Boolean isExit = false;
                        while (isExit == false)
                        {
                            LinkedListNode<ClientInfo> node = clientList.First;
                            node = node.Next;
                            if (node == null)
                            {
                                //循环踢出用户并关闭客户端连接
                                IPEndPoint iepLast = (IPEndPoint)clientList.Last.Value.Client.Client.RemoteEndPoint;
                                //this.SendMessage(iepLast, message);
                                this.SendCheckMessage(iepLast, "($KICK$)");

                                clientList.Last.Value.Client.Client.Close();
                                clientList.Remove(clientList.Last);

                                //先触发客户退出事件,在关闭客户端连接
                                if (clientCutConnection != null)
                                {
                                    ClientCutConnectionEventArgs eCloseLast = new ClientCutConnectionEventArgs();
                                    eCloseLast.Iep = iepLast;
                                    eCloseLast.CutTime = DateTime.Now;
                                    clientCutConnection(this, eCloseLast);
                                }
                                break;
                            }

                            //循环踢出用户并关闭客户端连接
                            IPEndPoint iep = (IPEndPoint)node.Previous.Value.Client.Client.RemoteEndPoint;
                            this.SendCheckMessage(iep, "($KICK$)");

                            node.Previous.Value.Client.Client.Close();
                            clientList.Remove(node.Previous);

                            //先触发客户退出事件,在关闭客户端连接
                            if (clientCutConnection != null)
                            {
                                ClientCutConnectionEventArgs eClose = new ClientCutConnectionEventArgs();
                                eClose.Iep = iep;
                                eClose.CutTime = DateTime.Now;

                                clientCutConnection(this, eClose);
                            }
                        }
                    }

                    listener.Stop();

                    result = 1;
                }
            }
            catch (Exception ex)
            {
                result = 0;
                this.WriteLog(this.logEnable, this.logPath, "发生未知异常:/r/n" + ex.ToString());
            }
            return result;
        }

        /// <summary>
        /// 踢出制定的客户端
        /// </summary>
        /// <param name="iep">要踢出客户端的 IpEndPoint</param>
        public Int32 Kick(IPEndPoint iep)
        {
            int result = 0;
            try
            {
                ClientInfo clientinfo = this.GetClientByIPEndPoint(iep);

                if (clientinfo != null)
                {
                    //往客户端发送字符串 "($KICK$)",客户端收到信息后 关闭连接.
                    this.SendCheckMessage((IPEndPoint)clientinfo.Client.Client.RemoteEndPoint, "($KICK$)");

                    //把客户端信息从,客户端列表里删除
                    clientList.Remove(clientinfo);

                    this.WriteLog(this.logEnable, this.logPath, "成功踢出客户端:" + iep + "时间:" + DateTime.Now);
                    //关闭客户端连接
                    clientinfo.Client.Client.Close();

                    //触发关闭客户端事件
                    if (clientCutConnection != null)
                    {
                        ClientCutConnectionEventArgs eClose = new ClientCutConnectionEventArgs();
                        eClose.Iep = iep;
                        eClose.CutTime = DateTime.Now;

                        clientCutConnection(this, eClose);
                    }
                    result = 1;
                }
                else
                {
                    //没有在客户端列表中找到 iep 
                    //发生未知异常,写入日志.
                    this.WriteLog(this.logEnable, this.logPath, "踢出客户端时,没有在客户端列表中找到客户端:" + iep + "时间:" + DateTime.Now);

                    result = 0;
                }
            }
            catch (Exception ex)
            {
                result = 0;
                this.WriteLog(this.logEnable, this.logPath, "发生未知异常:/r/n" + ex.ToString());
            }
            return result;
        }

        /// <summary>
        /// 给所有的客户端发送消息
        /// </summary>
        /// <param name="bytes">要发送消息的字节数组</param>
        /// <returns>返回结果 1 发送成功, 0 发送失败,-1 发送的消息长度太长</returns>
        public Int32 SendMessage(byte[] bytes)
        {
            Int32 result = 0;
            try
            {
                if (bytes.Length < 1024)
                {
                    lock (clientList)
                    {
                        foreach (ClientInfo ci in clientList)
                        {
                            try
                            {
                                TcpClient client = ci.Client;
                                NetworkStream netWorkStream = client.GetStream();

                                //byte[] packHeadBytes = Encoding.ASCII.GetBytes("($SAT:$)");
                                //byte[] packEndBytes = Encoding.ASCII.GetBytes("($:END$)");

                                //byte[] requestBytes = new byte[bytes.Length + packHeadBytes.Length + packEndBytes.Length];

                                //Array.Copy(packHeadBytes, 0, requestBytes, 0, packHeadBytes.Length);
                                //Array.Copy(bytes, 0, requestBytes, packHeadBytes.Length, bytes.Length);
                                //Array.Copy(packEndBytes, 0, requestBytes, packHeadBytes.Length + bytes.Length, packEndBytes.Length);

                                netWorkStream.BeginWrite(bytes, 0, bytes.Length, new AsyncCallback(SendCallback), netWorkStream);
                            }
                            catch (Exception ex)
                            {
                                //往客户端 client.Client.RemotEndPoint 发送数据时发生异常.写入日志.
                                this.WriteLog(this.logEnable, this.logPath, "往客户端:" + ci.Client.Client.RemoteEndPoint + "发送信息时发生 SocketException 时间" + DateTime.Now + " /r/n 原因:" + ex.Message);
                            }
                        }
                    }
                    result = 1;
                }
                else
                {
                    result = -1;
                }
            }
            catch (Exception ex)
            {
                result = 0;
                //未知异常:发送数据时发生异常,写入日志.
                this.WriteLog(this.logEnable, this.logPath, "发生未知异常:/r/n" + ex.ToString());
            }
            return result;
        }

        /// <summary>
        /// 给指定的客户端发送消息
        /// </summary>
        /// <param name="iep">客户端IpEndPoint</param>
        /// <param name="bytes">发送的字节数组</param>
        /// <returns>返回结果</returns>
        public Int32 SendMessage(IPEndPoint iep, byte[] bytes)
        {
            Int32 result = 0;
            try
            {
                if (bytes.Length < 1024)
                {
                    lock (clientList)
                    {
                        ClientInfo clientInfo = this.GetClientByIPEndPoint(iep);
                        if (clientInfo != null)
                        {
                            try
                            {
                                TcpClient client = clientInfo.Client;
                                NetworkStream netWorkStream = client.GetStream();

                                //byte[] packHeadBytes = Encoding.ASCII.GetBytes("($SAT:$)");
                                //byte[] packEndBytes = Encoding.ASCII.GetBytes("($:END$)");

                                //byte[] requestBytes = new byte[bytes.Length + packHeadBytes.Length + packEndBytes.Length];

                                //Array.Copy(packHeadBytes, 0, requestBytes, 0, packHeadBytes.Length);
                                //Array.Copy(bytes, 0, requestBytes, packHeadBytes.Length, bytes.Length);
                                //Array.Copy(packEndBytes, 0, requestBytes, packHeadBytes.Length + bytes.Length, packEndBytes.Length);

                                netWorkStream.BeginWrite(bytes, 0, bytes.Length, new AsyncCallback(SendCallback), netWorkStream);

                                result = 1;
                            }
                            catch (Exception ex)
                            {
                                result = 0;
                                //给客户端发送信息时发生异常写入日志.
                                this.WriteLog(this.logEnable, this.logPath, "网客户端:" + iep + "发送信息时发生 SocketException 时间" + DateTime.Now + " /r/n 原因:" + ex.Message);
                            }
                        }
                    }
                }
                else
                {
                    result = -1;
                }
            }
            catch (Exception ex)
            {
                result = 0;
                //发生未知异常,写入日志.
                this.WriteLog(this.logEnable, this.logPath, "发生位置异常:/r/n" + ex.ToString());
            }
            return result;
        }

        /// <summary>
        /// 给选中的客户端发送消息
        /// </summary>
        public Int32 SendMessage(IPEndPoint[] ieps, byte[] bytes)
        {
            Int32 result = 0;
            try
            {
                if (bytes.Length < 1024)
                {
                    lock (clientList)
                    {
                        foreach (IPEndPoint iep in ieps)
                        {
                            ClientInfo clientInfo = this.GetClientByIPEndPoint(iep);
                            if (clientInfo != null)
                            {
                                try
                                {
                                    TcpClient client = clientInfo.Client;
                                    NetworkStream netWorkStream = client.GetStream();

                                    //byte[] packHeadBytes = Encoding.ASCII.GetBytes("($SAT:$)");
                                    //byte[] packEndBytes = Encoding.ASCII.GetBytes("($:END$)");

                                    //byte[] requestBytes = new byte[bytes.Length + packHeadBytes.Length + packEndBytes.Length];

                                    //Array.Copy(packHeadBytes, 0, requestBytes, 0, packHeadBytes.Length);
                                    //Array.Copy(bytes, 0, requestBytes, packHeadBytes.Length, bytes.Length);
                                    //Array.Copy(packEndBytes, 0, requestBytes, packHeadBytes.Length + bytes.Length, packEndBytes.Length);

                                    netWorkStream.BeginWrite(bytes, 0, bytes.Length, new AsyncCallback(SendCallback), netWorkStream);
                                }
                                catch (Exception ex)
                                {
                                    //往客户端 clientInfo.Client 
                                    this.WriteLog(this.logEnable, this.logPath, "网客户端:" + clientInfo.Client.Client.RemoteEndPoint + "发送信息时发生 SocketException 时间" + DateTime.Now + " /r/n 原因:" + ex.Message);
                                }
                            }
                        }
                    }
                    result = 1; //给所有的客户端发送信息成功后返回 1
                }
                else
                {
                    result = -1;
                }
            }
            catch (Exception ex)
            {
                result = 0;
                //发生未知异常,写入日志.
                this.WriteLog(this.logEnable, this.logPath, "发生位置异常:/r/n" + ex.ToString());
            }
            return result;
        }
     

        #endregion

        #region -- 内部方法 --

        /// <summary>
        /// 接受到客户端的请求后触发的方法
        /// </summary>
        /// <param name="ar"></param>
        private void OnConnectRequest(IAsyncResult ar)
        {
            try
            {
                TcpListener listener = (TcpListener)ar.AsyncState;

                TcpClient client = new TcpClient();
                try
                {
                    client = listener.EndAcceptTcpClient(ar);
                }
                catch (ObjectDisposedException ex)
                {
                    //服务开启后再停止是触发改异常.写入日志.
                    this.WriteLog(this.logEnable, this.logPath, "停止服务时发生 ObjectDisposedException 异常:" + ex.Message + " 不需要处理." + "时间:" + DateTime.Now);
                    return;
                }
                clientList.AddLast(new ClientInfo(client));

                //客户端 client 连接成功后触发的事件
                if (this.connected != null)
                {
                    ClientConnectedEventArgs e = new ClientConnectedEventArgs();
                    e.Iep = (IPEndPoint)client.Client.RemoteEndPoint;
                    e.ConnectedTime = DateTime.Now;

                    this.connected(this, e);
                }
                //客户端连接成功写入日志
                this.WriteLog(this.logEnable, this.logPath, "客户端:" + client.Client.RemoteEndPoint + "连接成功! 时间:" + DateTime.Now);

                //初始化ThreadPack 存放连接客户端的信息
                byte[] buff = new byte[BUFFERSIZE];
                MemoryStream memStream = new MemoryStream();
                ThreadPack tp = new ThreadPack(buff, GetClientInfo(client), memStream);

                this.WaitData(tp); //客户端连接后等待客户端发送的数据

                listener.BeginAcceptTcpClient(new AsyncCallback(OnConnectRequest), listener); //继续监听客户端的连接
            }
            catch (Exception ex)
            {
                this.WriteLog(this.logEnable, this.logPath, "发生未知异常:/r/n" + ex.ToString());
                if (listener != null)//........................测试
                {
                    listener.Stop();//........................测试
                    Start();//........................测试
                }//........................测试
            }
        }

        /// <summary>
        /// 等待接受数据
        /// </summary>
        /// <param name="client">客户端</param>
        private void WaitData(ThreadPack tp)
        {
            try
            {
                tp.ClientInfo.Client.GetStream().BeginRead(tp.Buffer, 0, BUFFERSIZE, new AsyncCallback(OnGetData), tp);
            }
            catch (InvalidOperationException ex)
            {
                //踢出用户捕获此异常,暂时不处理,需要写入日志
                this.WriteLog(this.logEnable, this.logPath, "停止服务时发生 InvalidOperationException 异常:" + ex.Message + " 不需要处理." + "时间:" + DateTime.Now);
            }
            catch (Exception ex)
            {
                this.WriteLog(this.logEnable, this.logPath, "发生未知异常:/r/n" + ex.ToString());
            }
        }

        /// <summary>
        /// 数据接收后
        /// </summary>
        /// <param name="ar"></param>
        private void OnGetData(IAsyncResult ar)
        {
            ThreadPack tp = (ThreadPack)ar.AsyncState;
            try
            {
                Int32 readBytes = tp.ClientInfo.Client.GetStream().EndRead(ar);
                if (readBytes != 0)
                {
                    tp.MemStream.Write(tp.Buffer, 0, readBytes);
                    byte[] memBytes = tp.MemStream.ToArray();
                    //触发收到消息事件
                    if (this.receiveMesage != null)
                    {
                        ServerReceiveMessageEventArgs e = new ServerReceiveMessageEventArgs();
                        e.Iep = (IPEndPoint)tp.ClientInfo.Client.Client.RemoteEndPoint;
                        e.Buffer = memBytes;

                        this.receiveMesage(this, e);
                    }
                    tp.MemStream = new MemoryStream();
                }
            }
            catch (InvalidOperationException ex)
            {
                //踢出用户捕获此异常,暂时不处理 (如果调用方法失败不是由无效参数造成的)
                this.WriteLog(this.logEnable, this.logPath, "停止服务时发生 ObjectDisposedException 异常:" + ex.Message + " 不需要处理." + "时间:" + DateTime.Now);
            }
            catch (IOException ex)
            {
                IPEndPoint iep = tp.Iep;
                tp.ClientInfo.Client.Client.Close();
                // 客户端关闭应用进程,或关闭连接会触发改异常
                clientList.Remove(tp.ClientInfo);
                if (clientCutConnection != null)
                {
                    ClientCutConnectionEventArgs e = new ClientCutConnectionEventArgs();
                    e.Iep = iep;
                    e.CutTime = DateTime.Now;
                    clientCutConnection(this, e);
                }
                this.WriteLog(this.logEnable, this.logPath, "客户端:" + iep + "离开!   时间:" + DateTime.Now + "/r/n" + ex.Message + "不需要处理!");
                return;
            }
            catch (Exception ex)
            {
                this.WriteLog(this.logEnable, this.logPath, "发生未知异常:/r/n" + ex.ToString());
            }
            this.WaitData(tp);
        }

        /// <summary>
        /// 在客户端链表中查找指定的客户端信息
        /// </summary>
        /// <param name="client"></param>
        /// <returns></returns>
        private ClientInfo GetClientInfo(TcpClient client)
        {
            foreach (ClientInfo ci in clientList)
            {
                if (ci.Client.Equals(client))
                {
                    return ci;
                }
            }
            return null;
        }

        /// <summary>
        /// 在根据 IpEndPoint 获得 客户端信息
        /// </summary>
        /// <param name="iep">客户端 IP 端点</param>
        /// <returns></returns>
        private ClientInfo GetClientByIPEndPoint(IPEndPoint iep)
        {
            foreach (ClientInfo ci in clientList)
            {
                if (ci.Client.Client.RemoteEndPoint.Equals(iep))
                {
                    return ci;
                }
            }
            return null;
        }

        /// <summary>
        /// 发送信息回调函数
        /// </summary>
        /// <param name="ar"></param>
        private void SendCallback(IAsyncResult ar)
        {
            try
            {
                NetworkStream ns = (NetworkStream)ar.AsyncState;
                ns.EndWrite(ar);
            }
            catch (Exception ex)
            {
                this.WriteLog(this.logEnable, this.logPath, "发生未知异常:/r/n" + ex.ToString());
            }
        }

        /// <summary>
        /// 给所有客户端发送校验信息
        /// </summary>
        /// <param name="iep">客户端IpEndPoint</param>
        /// <param name="str">发送信息</param>
        private void SendCheckMessage(IPEndPoint iep, string str)
        {
            try
            {
                ClientInfo clientInfo = this.GetClientByIPEndPoint(iep);
                if (clientInfo != null)
                {
                    TcpClient client = clientInfo.Client;
                    NetworkStream netWorkStream = client.GetStream();

                    byte[] bytes = Encoding.ASCII.GetBytes(str);

                    netWorkStream.Write(bytes, 0, bytes.Length);
                }
            }
            catch (Exception ex)
            {
                //返回校验是否在线信息是发生异常
                this.WriteLog(this.logEnable, this.logPath, "发生未知异常:/r/n" + ex.ToString());
            }
        }

        /// <summary>
        /// 写入日志方法
        /// </summary>
        /// <param name="logEnable">是否写入日志</param>
        /// <param name="logPath">日志路径</param>
        /// <param name="content">日志内容</param>
        private void WriteLog(Boolean logEnable, String logPath, String content)
        {
            if (logEnable == true)
            {
                try
                {
                    StreamWriter sw = File.AppendText(logPath);
                    lock (sw)
                    {
                        try
                        {
                            sw.WriteLine(content);
                        }
                        catch
                        { }
                        finally
                        {
                            sw.Close();
                        }
                    }
                }
                catch (Exception)
                {
                    //此异常是因为多个线程同时写日志时发生,不需要处理.
                }
            }
        }

        private void CheckTime()
        {
            while (true)
            {
                Thread.Sleep(this.timeOut / 2);//在判断是否掉线时间的一半时间校验一次

                if (clientList.Count == 0)
                {
                    continue;
                }

                LinkedListNode<ClientInfo> node = clientList.First;

                while (true)
                {
                    if (node == null)
                    {
                        break;
                    }

                    TimeSpan span = DateTime.Now - node.Value.LastCheckTime;

                    if (node == clientList.Last)
                    {
                        if (span.TotalMilliseconds > this.timeOut) //删除
                        {
                            IPEndPoint iep = (IPEndPoint)node.Value.Client.Client.RemoteEndPoint;
                            node.Value.Client.Client.Close();
                            clientList.Remove(node);

                            //触发客户端断开事件
                            if (clientCutConnection != null)
                            {
                                ClientCutConnectionEventArgs eClose = new ClientCutConnectionEventArgs();
                                eClose.Iep = iep;
                                eClose.CutTime = DateTime.Now;

                                clientCutConnection(this, eClose);
                            }

                            break;
                        }
                    }
                    else if (node != clientList.First)
                    {
                        if (span.TotalMilliseconds > this.timeOut) //删除
                        {
                            node = node.Previous;

                            IPEndPoint iep = (IPEndPoint)node.Next.Value.Client.Client.RemoteEndPoint;
                            node.Next.Value.Client.Client.Close();
                            clientList.Remove(node.Next);

                            //触发客户端断开事件
                            if (clientCutConnection != null)
                            {
                                ClientCutConnectionEventArgs eClose = new ClientCutConnectionEventArgs();
                                eClose.Iep = iep;
                                eClose.CutTime = DateTime.Now;
                                clientCutConnection(this, eClose);
                            }
                        }
                    }
                    else
                    {
                        if (span.TotalMilliseconds > this.timeOut) //删除
                        {
                            node = node.Next;
                            IPEndPoint iep = (IPEndPoint)node.Previous.Value.Client.Client.RemoteEndPoint;
                            clientList.Remove(node.Previous);

                            //触发客户端断开事件
                            if (clientCutConnection != null)
                            {
                                ClientCutConnectionEventArgs eClose = new ClientCutConnectionEventArgs();
                                eClose.Iep = iep;
                                eClose.CutTime = DateTime.Now;

                                clientCutConnection(this, eClose);
                            }
                            continue;
                        }
                    }
                    node = node.Next;
                }
            }
        }
        #endregion
    }
}
